// import React, { useEffect } from 'react';
// import ReactDOM from 'react-dom';
import { Graph } from '@antv/x6';
import { RERENDER_EVENT, EventItemType, CanvasMode } from '../dict';
import { debounceTime, map, tap, scan, filter, take } from 'rxjs/operators';
import { BehaviorSubject, fromEventPattern, timer, Subscription, merge } from 'rxjs';
import { newGrapNodeMeta, optimizeJSON, graphOptions } from './graph-utils.js';
import ReactNode from './ReactNode';
// import './edge.less';

// 支持 Graph.fromJSON()
// 方法一： 注册返回 React 组件的函数
// Graph.registerReactComponent('react-node', (node) => {
//   const data = node.getData();
//   return <ReactNode type={data.type} text={data.desc || EventItemType.getDesc(data.type)} />;
// });

// BehaviorSubject，它有一个 "当前值" 的概念。它保存了发送给消费者的最新值
// 并且当有新的观察者订阅时，会立即从 BehaviorSubject 那接收到 "当前值"

const Edge_Default_Color = '#888';
const Edge_Selected_Color = '#FF9400';
const Edge_Passed_Color = '#74B8A9FF';

export default class WorkFlowGraph {
  //
  graph;

  wrapper;

  container;

  miniMapContainer;

  // 当前画布右键点击信息
  contextMenuInfo$ = new BehaviorSubject(null);

  // 待复制的节点 id
  copyableNodeId$ = new BehaviorSubject('');

  // 实验图加载状态
  loading$ = new BehaviorSubject(false);

  // 工作流数据
  workflowGraph$ = new BehaviorSubject(null);

  // 工作流数据订阅
  workflowGraphSub;

  // 主动触发的重新渲染订阅
  reRenderSub;

  // edge click 事件
  edgeClickSub;

  // node mouse over 事件
  nodeMouseoverSub;

  // 右键点击事件 订阅
  nodeContextMenuSub;
  edgeContextMenuSub;
  graphContextMenuSub;

  //
  contextMenuSub;

  //
  selectNodeSub;

  //
  connectNodeSub;

  //
  connectionRemovedSub;

  moveNodesSub;

  deleteNodeOrEdgeSub;

  copyNodeSub;

  edgeMouseoverSub;

  blankClickSub;

  constructor(mode) {
    this.canvasMode = mode || CanvasMode.Editor;
  }

  isGraphReady() {
    return !!this.graph;
  }

  // 渲染画布
  renderGraph = (wrapper, container, miniMapContainer, dispatch) => {
    this.wrapper = wrapper;
    this.container = container;
    this.dispatch = dispatch;
    this.miniMapContainer = miniMapContainer;
    this.workflowGraphSub = this.workflowGraph$
      // .pipe(
      //   filter((x) => !!x), // 过滤出有效数据
      //   take(1), // 只做一次挂载渲染
      // )
      .subscribe((graphData) => {
        if (!this.graph) {
          this.render();
        }
      });

    // 监听主动触发的重新渲染事件，避免从 IDE 返回后画布消失
    this.reRenderSub = fromEventPattern(
      (handler) => {
        window.addEventListener(RERENDER_EVENT, handler);
      },
      (handler) => {
        window.removeEventListener(RERENDER_EVENT, handler);
      },
    ).subscribe(this.handlerResize);
  };

  render() {
    const that = this;
    let graphOptionsObj = { ...graphOptions };
    if (this.canvasMode === CanvasMode.Run)
      graphOptionsObj = { ...graphOptions, interacting: false };
    const graph = new Graph({
      ...graphOptionsObj,
      container: this.container,
      width: this.wrapper.clientWidth,
      height: this.wrapper.clientHeight,
      // x6 bugs: 使用官方推荐的 ReactNode渲染方式，导致加入miniMap报错
      // minimap: {
      //   enabled: true,
      //   container: this.miniMapContainer,
      // },
      // 支持 Graph.fromJSON()
      // 方法二： getReactComponent hook
      getReactComponent: (node) => {
        const data = node.getData();
        return (
          <ReactNode
            type={data.type}
            canvasMode={that.canvasMode}
            text={data.desc || EventItemType.getDesc(data.type)}
            updateSelectedState={that.updateSelectedState.bind(that)}
          />
        );
      },
    });

    this.graph = graph;
    if (this.workflowGraph$.getValue()) {
      this.graph.fromJSON(this.workflowGraph$.getValue());
    }

    this.afterLayout();

    if (graph.isFrozen()) {
      graph.unfreeze();
    }

    requestAnimationFrame(() => {
      graph.center();
      // graph.centerContent();
    });

    // 处理窗口缩放
    this.windowResizeSub = fromEventPattern(
      (handler) => {
        window.addEventListener('resize', handler);
      },
      (handler) => {
        window.removeEventListener('resize', handler);
      },
    ).subscribe(this.resizeGraph);

    // 处理右键菜单
    const nodeContextMenuObs = fromEventPattern(
      (handler) => {
        graph.on('node:contextmenu', (data) => {
          handler({ type: 'node', data });
        });
      },
      (handler) => {
        graph.off('node:contextmenu', handler);
      },
    );

    const edgeContextMenuObs = fromEventPattern(
      (handler) => {
        graph.on('edge:contextmenu', (data) => {
          handler({ type: 'edge', data });
        });
      },
      (handler) => {
        graph.off('edge:contextmenu', handler);
      },
    );
    const graphContextMenuObs = fromEventPattern(
      (handler) => {
        graph.on('blank:contextmenu', (data) => {
          handler({ type: 'graph', data });
        });
      },
      (handler) => {
        graph.off('edge:contextmenu', handler);
      },
    );

    this.nodeContextMenuSub = nodeContextMenuObs.subscribe((data) => {
      this.onNodeContextMenu(data);
    });

    this.edgeContextMenuSub = edgeContextMenuObs.subscribe((data) => {
      this.onEdgeContextMenu(data);
    });

    this.graphContextMenuSub = graphContextMenuObs.subscribe((data) => {
      this.onGraphContextMenu(data);
    });

    this.contextMenuSub = merge(
      nodeContextMenuObs,
      edgeContextMenuObs,
      graphContextMenuObs,
    ).subscribe((data) => {
      if (this.validateContextMenu(data)) {
        const {
          data: { e },
        } = data;
        this.contextMenuInfo$.next({ ...data });
        this.onContextMenu(data);
      }
    });

    // 处理节点选中事件
    this.selectNodeSub = fromEventPattern(
      (handler) => {
        graph.on('selection:changed', handler);
      },
      (handler) => {
        graph.off('selection:changed', handler);
      },
    ).subscribe((args) => {
      const { removed, selected } = args;
      // setCellsSelectedStatus(removed, false);
      // setCellsSelectedStatus(selected, true);
      this.onSelectNodes(selected);
    });

    // 处理产生连线事件
    this.connectNodeSub = fromEventPattern(
      (handler) => {
        graph.on('edge:connected', handler);
      },
      (handler) => {
        graph.off('edge:connected', handler);
      },
    ).subscribe((args) => {
      this.onConnectNode(args);
    });

    // 处理连线删除事件
    this.connectionRemovedSub = fromEventPattern(
      (handler) => {
        graph.on('edge:removed', handler);
      },
      (handler) => {
        graph.off('edge:removed', handler);
      },
      // eslint-disable-next-line consistent-return
    ).subscribe((args) => {
      this.onConnectionRemoved(args);
    });

    // 处理节点移动事件
    let moveStarted = false; // 因为需要对移动事件做分片，区分两次移动事件，所以引入一个记录移动开始的变量
    this.moveNodesSub = fromEventPattern(
      (handler) => {
        graph.on('node:change:position', handler);
      },
      (handler) => {
        graph.off('node:change:position', handler);
      },
    )
      .pipe(
        tap((args) => {
          this.onMoveNodeStart(args);
        }),
        scan((accum, args) => {
          const currentAccum = !moveStarted ? [] : accum;
          const { node } = args;
          const { id } = node;
          const matchItemIndex = currentAccum.findIndex((item) => item.id === id);
          if (matchItemIndex > -1) {
            currentAccum.splice(matchItemIndex, 1, { id, data: args });
          } else {
            currentAccum.push({ id, data: args });
          }
          return currentAccum;
        }, []),
        tap(() => {
          if (!moveStarted) {
            moveStarted = true;
          }
        }),
        debounceTime(300),
        tap(() => {
          if (moveStarted) {
            moveStarted = false;
          }
        }),
        map((items) => items.map((item) => item.data)),
      )
      .subscribe((movedNodes) => {
        this.onMoveNodes(movedNodes);
      });

    // 处理删除节点或连线事件
    this.deleteNodeOrEdgeSub = fromEventPattern(
      (handler) => {
        graph.bindKey(['delete', 'backspace'], handler);
      },
      () => {
        graph.unbindKey(['delete', 'backspace']);
      },
    ).subscribe(() => this.onDeleteSelectedCells());

    // 处理节点复制事件
    this.copyNodeSub = fromEventPattern(
      (handler) => {
        graph.bindKey(['command+c', 'ctrl+c', 'command+v', 'ctrl+v'], handler);
      },
      () => {
        graph.unbindKey(['command+c', 'ctrl+c', 'command+v', 'ctrl+v']);
      },
    ).subscribe((args) => {
      const [, action] = args;
      const selectedCells = graph
        .getSelectedCells()
        .filter((cell) => this.validateNodeCopyable(cell));
      const copyableNodeId = this.copyableNodeId$.getValue();
      let copyableNode;
      if (copyableNodeId) {
        copyableNode = graph.getCellById(copyableNodeId);
      }
      switch (action) {
        case 'command+c':
        case 'ctrl+c':
          if (selectedCells?.length) {
            this.setCopyableNodeId(selectedCells[0].id); // 当前只支持单节点的复制粘贴
          }
          break;
        case 'command+v':
        case 'ctrl+v':
          if (copyableNode) {
            this.onCopyNode(copyableNode);
          }
          break;
        default:
      }
    });

    // edge mouseover 事件
    const edgeMouseenterObs = fromEventPattern(
      (handler) => {
        graph.on('edge:mouseenter', handler);
      },
      (handler) => {
        graph.off('edge:mouseenter', handler);
      },
    ).pipe(map((ev) => ({ ...ev, mouseAction: 'enter' })));

    const edgeMouseleaveObs = fromEventPattern(
      (handler) => {
        graph.on('edge:mouseleave', handler);
      },
      (handler) => {
        graph.off('edge:mouseleave', handler);
      },
    ).pipe(map((ev) => ({ ...ev, mouseAction: 'out' })));

    this.edgeMouseoverSub = merge(edgeMouseenterObs, edgeMouseleaveObs).subscribe(
      ({ e, edge, view, mouseAction }) => {
        // console.log(e, edge, view, mouseAction);
      },
    );

    const nodeMouseenterObs = fromEventPattern(
      (handler) => {
        graph.on('node:mouseenter', handler);
      },
      (handler) => {
        graph.off('node:mouseenter', handler);
      },
    ).pipe(map((ev) => ({ ...ev, mouseAction: 'enter' })));

    const nodeMouseleaveObs = fromEventPattern(
      (handler) => {
        graph.on('node:mouseleave', handler);
      },
      (handler) => {
        graph.off('node:mouseleave', handler);
      },
    ).pipe(map((ev) => ({ ...ev, mouseAction: 'out' })));

    this.nodeMouseoverSub = merge(nodeMouseenterObs, nodeMouseleaveObs).subscribe(
      ({ e, edge, view, mouseAction }) => {
        if (this.canvasMode === CanvasMode.Run) return;
        if (mouseAction === 'enter') {
          this.changePortsVisible(true);
        } else {
          this.changePortsVisible(false);
        }
      },
    );

    // edge click 事件
    this.edgeClickSub = fromEventPattern(
      (handler) => {
        graph.on('edge:click', handler);
      },
      (handler) => {
        graph.off('edge:click', handler);
      },
    ).subscribe((event) => {
      this.onClickEdge(event);
    });

    // 画布上空白区域点击事件
    this.blankClickSub = fromEventPattern(
      (handler) => {
        graph.on('blank:click', handler);
      },
      (handler) => {
        graph.off('blank:click', handler);
      },
    ).subscribe((event) => {
      this.onGraphClick();
    });
  }

  // 添加 节点
  requestAddNode({ clientX, clientY, itemType }) {
    if (this.graph) {
      const pos = this.graph.clientToLocal(clientX, clientY);
      const newNodeMeta = newGrapNodeMeta({
        x: pos.x,
        y: pos.y,
        itemType,
        updateSelectedState: this.updateSelectedState.bind(this),
      });
      const graphNode = this.graph.addNode(newNodeMeta);
      graphNode.setData({
        formKey: {
          projectId: '',
          pageId: '',
          pageNameCh: '',
          pageNameEn: '',
          pagePath: '',
          pageType: '',
        },
        memberList: [],
        sendList: [],
        ApprovalType: 'orSign',
      });
      if (itemType === 'EndEvent') return;
      this.dispatch({
        type: 'wfeditor/addCellEffect',
        payload: { id: graphNode.id, type: graphNode.data.type },
      });
    }
  }

  updateSelectedState(id) {
    const cellsNode = this.graph.getCells();
    cellsNode.forEach((cell) => {
      if (cell.shape === 'react-shape' && cell.id === id) cell.setData({ selected: true });
      else if (cell.shape === 'react-shape' && cell.data.selected)
        cell.setData({ selected: false });
      else if (cell.shape === 'edge') {
        if (cell.data && cell.data.selected) {
          cell.setPropByPath('attrs/line/stroke', Edge_Default_Color);
          cell.setData({ selected: false });
        }
      }
    });
  }

  // 合并线的运行状态
  updageEdgeRunState(highLightedFlows) {
    const cellsNode = this.graph.getCells();
    cellsNode.forEach((cell) => {
      if (cell?.isEdge() && highLightedFlows.indexOf(cell.id) !== -1) {
        cell.setPropByPath('attrs/line/stroke', Edge_Passed_Color);
        cell.toFront();
      }
    });
  }

  // 边点击事件
  onClickEdge({ e, edge, view }) {
    if (this.canvasMode === CanvasMode.Run) return;
    this.dispatch({
      type: 'wfeditor/selected',
      payload: { id: edge.id, type: EventItemType.Edge },
    });
    const cellsNode = this.graph.getCells();
    cellsNode.forEach((cell) => {
      if (cell.shape === 'react-shape') cell.setData({ selected: false });
      else if (cell.shape === 'edge' && cell.id === edge.id) {
        cell.setData({ selected: true });
        cell.setPropByPath('attrs/line/stroke', Edge_Selected_Color);
      } else if (cell.shape === 'edge') {
        cell.setData({ selected: false });
        cell.setPropByPath('attrs/line/stroke', Edge_Default_Color);
      }
    });
  }

  // 点击画布空白区域
  onGraphClick() {
    if (this.canvasMode === CanvasMode.Run) return;
    this.dispatch({
      type: 'wfeditor/selected',
      payload: { id: null, type: null },
    });
    this.graph
      .getCells()
      .filter((c) => c.data?.selected)
      .forEach((c) => {
        if (c.isNode()) c.setData({ selected: false });
        else if (c.isEdge()) {
          c.setPropByPath('attrs/line/stroke', Edge_Default_Color);
        }
      });
  }

  // 按下删除键的回调，默认参数为当前选中的节点和边
  onDeleteSelectedCells() {
    // 选中态设置在 每一个 node.data.selected
    const cells = this.graph.getCells();
    const nodes = cells.filter((cell) => cell.isNode() && cell.data?.selected);
    const edges = cells.filter((cell) => cell.isEdge() && cell.data?.selected);
    if (nodes && nodes.length > 0) {
      const node = nodes[0];

      // 发起流程 和 结束节点不能删除
      if (
        node.data?.type === EventItemType.StartEvent ||
        node.data?.type === EventItemType.EndEvent
      )
        return;

      this.dispatch({
        type: 'wfeditor/deleteCellEffect',
        payload: { id: node.id },
      });
      this.graph.removeCells(nodes);
    }
    if (edges && edges.length > 0) {
      const edge = edges[0];
      this.dispatch({
        type: 'wfeditor/deleteCellEffect',
        payload: { id: edge.id },
      });
      this.graph.removeCells(edges);
    }
  }

  afterLayout() {
    if (process.env.NODE_ENV === 'development') {
      console.log('[GraphCore] call afterLayout');
    }
  }

  resizeGraph = () => {
    const { graph, wrapper } = this;
    if (graph && wrapper) {
      requestAnimationFrame(() => {
        const width = wrapper.clientWidth;
        const height = wrapper.clientHeight;
        graph.resize(width, height);
      });
    }
  };

  onSelectNodes(nodes) {
    if (nodes.length > 0) {
      const node = nodes[0];
      this.dispatch({
        type: 'wfeditor/selected',
        payload: { id: node.id, type: node.constructor.name },
      });
      this.changePortsVisible(false);
    } else {
      this.dispatch({
        type: 'wfeditor/selected',
        payload: { id: null, type: null },
      });
    }
  }

  // 添加了连接线
  onConnectNode({ edge }) {
    edge.setData({
      type: EventItemType.Edge,
      editType: 'edit',
      meetType: 'one',
      conditionList: [],
      equation: {
        componentColumns: [],
        conditionExpression: '',
      },
    });
    this.dispatch({
      type: 'wfeditor/addCellEffect',
      payload: {
        id: edge.id,
        type: EventItemType.Edge,
      },
    });
  }

  // 删除了连接线
  onConnectionRemoved({ edge }) {
    // console.log("删除了连接线", edge)
    // this.dispatch({
    //   type: 'wfeditor/deleteCellEffect',
    //   payload: { id: edge.id },
    // });
  }

  validateContextMenu(data) {
    return !!data;
  }

  // 响应 右键 点击事件
  onContextMenu(data) {
    // if (process.env.NODE_ENV === 'development') {
    //   console.log('[GraphCore] context menu info:', data);
    // }
  }

  onNodeContextMenu({ data }) {
    // 右键点击节点，选中当前节点
    if (this.canvasMode === CanvasMode.Run) return;
    const id = data.cell.id;
    const type = data.node?.data.type;
    this.dispatch({ type: 'wfeditor/selected', payload: { id, type } });
    this.updateSelectedState(id);
  }

  onEdgeContextMenu({ data }) {
    // 右键点击线，选中线
    this.onClickEdge(data);
  }

  onGraphContextMenu(data) {
    // 右键点击 graph，清空选择
    this.onGraphClick();
  }

  // 缩放画布
  zoom = (factor) => {
    if (typeof factor === 'number') {
      this.graph?.zoom(factor);
    } else if (factor === 'fit') {
      this.graph?.zoomToFit({ padding: 12 });
    } else if (factor === 'real') {
      this.graph?.scale(1);
      this.graph?.centerContent();
    }
  };

  onMoveNodeStart(args) {
    // if (process.env.NODE_ENV === 'development') {
    //   console.log('[GraphCore] move node start:', args);
    // }
  }

  onMoveNodes(args) {
    const [firstEvent] = args;
    if (firstEvent) {
      this.dispatch({
        type: 'wfeditor/updateCellEffect',
        payload: { id: firstEvent.node.id },
      });
    }
  }

  // 校验节点是否可复制，为 true 则可被用于复制
  validateNodeCopyable(node) {
    // if (process.env.NODE_ENV === 'development') {
    //   console.log('[GraphCore] validate node copyable:', node, node.constructor.name);
    // }
    // 边 Edge 不能复制
    if (node && node.constructor && node.constructor.name !== 'Edge') return true;
    return false;
  }

  // 按下粘贴键的回调，默认参数为待复制的节点
  onCopyNode(copyNode) {
    // if (process.env.NODE_ENV === 'development') {
    //   console.log('[GraphCore] paste node:', copyNode);
    // }
    const CtorMap = {
      // VerifyDiamond: EventItemType.VerifyTask,
      VerifyRect: EventItemType.VerifyTask,
      EndCircle: EventItemType.EndEvent,
      WorkRect: EventItemType.WorkTask,
      StartRect: EventItemType.StartEvent,
    };
    const pos = copyNode.position();
    const localPos = this.graph.localToClient(pos.x, pos.y);
    this.requestAddNode({
      clientX: localPos.x + 50,
      clientY: localPos.y + 50,
      itemType: CtorMap[copyNode.constructor.name],
    });
  }

  // 设置待复制的节点 id
  setCopyableNodeId = (id) => {
    this.copyableNodeId$.next(id);
  };

  changePortsVisible(visible) {
    const ports = this.container.querySelectorAll('.x6-port-body');
    for (let i = 0, len = ports.length; i < len; i = i + 1) {
      ports[i].style.visibility = visible ? 'visible' : 'hidden';
    }
  }

  toJSON() {
    // 导出的JSON格式包含了已定义的图元的属性，需要在导出的时候删除
    const beforeOpt = this.graph.toJSON();
    console.log('before opt :', JSON.stringify(beforeOpt));
    return optimizeJSON(beforeOpt);
  }

  // 用户从 属性面板更新 graph node 的 props 数据
  // updateGraphProps 用法：
  //   id: 可以是具体id | *， *表示所有cell
  //   propsByPath:
  //      'data':  将 value 的值直接调用 cell.setData();
  //      'data/name': 修改 cell 的标题时需要用这种，同时会修改 cell.data 值
  //      {cell的属性值}: 例如： 'attrs/label/text'，修改 cell 属性值
  //   value:
  updateGraphProps(id, propsByPath, value) {
    const cellsNode = this.graph.getCells();
    cellsNode
      .filter((node) => id === '*' || node.id === id)
      .forEach((node) => {
        // 但修改名字的时候，需要同时修改到画布上
        if (propsByPath === 'data/name') {
          node.setPropByPath('attrs/label/text', value);
          node.setData({ name: value });
        } else if (propsByPath === 'data') {
          node.setData({ ...node.data, ...value }, { overwrite: true });
        } else node.setPropByPath(propsByPath, value);
      });
  }

  // 清空右键菜单信息
  clearContextMenuInfo = () => {
    this.contextMenuInfo$.next(null);
  };

  // 将画布上的点转换成相对于 offset parent 的点
  graphPointToOffsetPoint(graphPoint) {
    if (this.graph) {
      const point = this.graph.localToClient({ x: graphPoint.x, y: graphPoint.y });
      point.x -= 58;
      point.y -= 104;
      return point;
    }
    return { x: -100, y: -100 };
  }

  dispose() {
    this.wrapper = null;
    this.container = null;
    this.dispatch = null;
    this.workflowGraphSub.unsubscribe();
    this.reRenderSub.unsubscribe();
    this.selectNodeSub.unsubscribe();
    this.connectNodeSub.unsubscribe();
    this.connectionRemovedSub.unsubscribe();
    this.connectionRemovedSub.unsubscribe();
    this.moveNodesSub.unsubscribe();
    this.deleteNodeOrEdgeSub.unsubscribe();
    this.copyNodeSub.unsubscribe();
    this.edgeMouseoverSub.unsubscribe();
    this.edgeClickSub.unsubscribe();
    this.nodeMouseoverSub.unsubscribe();
    this.blankClickSub.unsubscribe();
    this.contextMenuSub.unsubscribe();
    this.nodeContextMenuSub.unsubscribe();
    this.edgeContextMenuSub.unsubscribe();
    this.graphContextMenuSub.unsubscribe();
    if (this.graph) {
      this.graph.clearCells();
      this.graph.dispose();
      delete this.graph;
      this.graph = null;
    }
  }
}
